Exploratory Data Analysis of Renthop data.

This is the initial exploratory section of the project. It also creates some simple features.

Remove all objects

rm(list = ls())

Load libraries

# Clear any previously loaded packages
pkgs_1 = names(sessionInfo()$otherPkgs)
pkgs_2 = paste('package:', pkgs_1, sep = "")
invisible(if (length(pkgs_1) > 0){
  lapply(pkgs_2, detach, character.only = TRUE, unload = TRUE)
})
library("tidyverse")
library("rjson")
library("rmarkdown")
library("lubridate")
library("ggplot2")
library("maps")
library("mapproj")
library("RColorBrewer")
library("wordcloud")

Load data

Save as an RDS file, so that we don’t need to constantly reload this data for other scripts.

data_train <- rjson::fromJSON(file= "../data/train.json" )
data_test <- rjson::fromJSON(file= "../data/test.json" )
# unlist every variable except `photos` and `features` and convert to tibble
vars_train <- setdiff(names(data_train), c("photos", "features"))
vars_test <- setdiff(names(data_test), c("photos","features"))
data_train <- map_at(data_train, vars_train, unlist) %>% tibble::as_tibble(.) %>%
  mutate(section = 'train')
data_test <- map_at(data_test, vars_test, unlist) %>% tibble::as_tibble(.) %>%
  mutate(interest_level = 'none',
         section = 'test')
data <- rbind(data_train,data_test)
saveRDS(data, file = "../data/objects/data.rds")

View the data.

data[1:3,]

There is more than one feature or photo for most of the listings.

Check for missing values

## There aren't any missing values in both train and test.
print(data_na <- data %>% summarise_each(funs(sum(is.na(.)))))

Exploration / Simple Features

Check Header

listings <- data %>% 
  select(-features, - photos)
head(listings)

Check count of listings in different interest levels

listings %>% filter(section == "train") %>% 
  group_by(interest_level) %>% 
  count()

Renthop didn’t specify what methodology they used to label each listing among high, medium, and low. Yet, they didn’t do so equally. There are many more low than medium, and more medium than high. This dataset is unbalanced.

Create Features

#Counts how many photos each listing has
photos_count <- map_int(data[['photos']], function(x) length(unlist(x)))
photos_count <- tibble::as_tibble(photos_count)
listings$photos_count <- photos_count$value
listings %>% rename(value = photos_count)
feature_count <- map_int(data[['features']], function(x) length(unlist(x)))
feature_count <- tibble::as_tibble(feature_count)
listings$feature_count <- feature_count$value
listings %>% rename(value = feature_count)
#Room Count
listings <- mutate(listings,
                   dates_formatted = ymd_hms(listings$created),
                   total_rooms = bedrooms + bathrooms + 1,
                   beds_bath_ratio = ifelse(bathrooms > 0, bedrooms / bathrooms, NA),
                   # zero bedrooms and bathrooms must be an efficiency apartment
                   price_room_ratio = ifelse(total_rooms >0 , price / total_rooms, price),
                   price_bath_ratio = ifelse( bathrooms > 0, price / bathrooms, NA),
                   price_photos_ratio = ifelse(photos_count > 0, price / photos_count, NA),
                   price_feature_ratio = ifelse(feature_count > 0, price / feature_count, NA),
                   created_ymd = ymd(as.Date(created)),
                   created_dow = wday(created),
                   created_h = hour(created)
                   )

Analyze the intial features to better understand the data

# Make interest_level a factor so that later output will display in low, medium, high order
listings <- listings %>%
  mutate(interest_level = factor(interest_level, levels = c("low", "medium", "high"))
         )
  
group_by(listings, interest_level) %>%
  filter(section == 'train') %>%
    summarize(
              count = n(),
              mean_price = mean(price),
              min_price = min(price),
              max_price = max(price),
              stdev_price = sd(price), 
              q_25 = quantile(price, .25), 
              median_price = median(price), 
              q_75 = quantile(price, .75), 
              q_99 = quantile(price, .99),
              median_bedrooms = median(bedrooms),
              median_total_rooms = median(total_rooms),
              q_99_total_rooms = quantile(total_rooms, .99),
              median_photo_count = median(photos_count),
              median_features = median(feature_count)
             )

Unsurprisingly, listings with lower prices and more rooms receive more interest. Note the maximum price for a low interest apartment is $4,490,000. Even for N.Y.C. this is hard to believe.

Let’s take a closer look at the price distribution with a boxplot.

Simple Boxplot

price_box <- listings %>%
  filter(section == 'train')
ggplot(price_box, aes(interest_level, price)) + geom_boxplot(varwidth = TRUE)

As expected a few extreme outliers distort the results. We need to limit the y axis range in ggplot2.

ggplot(price_box, aes(interest_level, price)) + geom_boxplot(varwidth = TRUE) + coord_cartesian(ylim = c(0, 20000)) + ggtitle("Interest Level Spread")

We can see the those listings with low interest are higher in general, but also have a large number of expensive outliers, which are also reasonable for NYC. The high interest listings have a lower median price, and a narrower distribution.

Density Plot

Let’s look at price distribution with a density plot.

#Price density plot
listings %>% filter(price <= quantile(listings$price, 0.95), section == 'train') %>%
ggplot(aes(x = price, fill = interest_level)) +
    geom_histogram(aes(y = ..density..), binwidth= 200, position = "identity", color = "red", size = .04) +
    facet_grid(.~interest_level) +
    ggtitle("Price Probability Density")

We can see from this plot that the listings with higher interest are much more concentrated around low price. Also, like a lot of transaction data it is not normal. It’s clearly skewed to the right. This happens because on the lower side the price distribution is bound by zero, yet there is a lot of room to ask for high prices. No one would pay you to live in their apartment.

Proportion of Bedroom in Interest Level

#summary table for bar chart  
filter(listings, section == 'train') %>%
group_by(bedrooms, interest_level) %>%
  summarize(room_count = n()) %>% 
  ungroup() %>% 
  group_by(interest_level) %>% 
  mutate(total_bedrooms = sum(room_count)) %>%
  mutate(p = room_count/total_bedrooms) %>%
  ggplot(aes(bedrooms, p)) +
  geom_bar(aes(fill = interest_level), stat = "identity")  +
  facet_grid(.~interest_level) +
  ylab("proportion of bedrooms in interest level")

Two bedrooms are the highest proportion of all of the interest levels.

Prices by Number of Bedrooms

filter(listings, price <= quantile(listings$price, 0.999), section == 'train') %>%
ggplot(aes(x = bedrooms, y = price)) +
  geom_jitter(aes(color = interest_level), alpha = 0.02) +
  facet_grid(.~interest_level)

The price of apartments with more bedrooms increases. It increases faster for low interest apartments. Interest in an apartment decreases quickly after the price gets to about $8,000 per month.

Price to Room Ratio

filter(listings, price <= quantile(listings$price, 0.95), section == 'train') %>%
ggplot(aes(x = total_rooms, y = price_room_ratio)) +
  geom_jitter(aes(color = interest_level), alpha = 0.02) +
  facet_grid(.~interest_level)

Feature Count Density

#features by interest
filter(listings, price <= quantile(listings$price, 1.00), section == 'train') %>%
ggplot(aes(x = feature_count)) +
  geom_histogram(aes(y = ..density.., fill = interest_level), 
                 bins = 40, position = "identity", color = "white", size = 0.07) +
  facet_grid(.~interest_level) +
  guides(color = FALSE)

The density of feature count doesn’t seem to vary greatly by interest level. Most listings have about 5 features. Surprisingly, the high interest level listings have the highest proportion of zero listed feature listings. They must have something obvious to offer, such that the lister doens’t think there is a need to list features.

Price by Number of Features

#price by number of features
filter(listings, price <= quantile(listings$price, 0.99), section == 'train') %>%
ggplot(aes(x = feature_count, y = price)) +
  geom_jitter(aes(color = interest_level), alpha = .10) +
  facet_grid(.~interest_level) +
  ggtitle("Variation of Price at different feature count and interest levels") +
geom_smooth()

Excluding outliers there is a clear positive relationship between feature_count and price. If you want more money then you need to explain why your apartment is worth it. There are also many high priced apartments with a low interest level and a small number of features listed. That makes sense. Work harder:)

Photo Count Density

#photos by interest
filter(listings, price <= quantile(listings$price, 1.00), section == 'train') %>%
ggplot(aes(x = photos_count)) +
  geom_histogram(aes(y = ..density.., fill = interest_level), 
                 bins = 40, position = "identity", color = "white", size = 0.07) +
  facet_grid(.~interest_level) +
  guides(color = FALSE)

The density of photo count doesn’t seem to vary greatly by interest level. Most listings have about 5 photos

Price by Number of Photos

#price by number of photos
filter(listings, price <= quantile(listings$price, 0.99), section == 'train') %>%
ggplot(aes(x = photos_count, y = price)) +
  geom_jitter(aes(color = interest_level), alpha = .10) +
  facet_grid(.~interest_level) +
  ggtitle("Variation of Price at different photos count and interest levels") +
geom_smooth()

For the low interest listings there is a clear positive relationship between the number of phots in the listing and price. The people who create the listing must realize they need to work harder. The relationship is less clear for the medium and low interest listings, although it’s still there. It is clear however, that the first 10 photos are strongly associated with a larger price. It would be interesting to know how long each listing was on the market. Then we could have a better understanding if the prices asked were reasonable market prices or not. Are the listers able to ask a higher price because they spent more time uploading photos, or is the price already higher and they need to show why that is?

Listings Creation Date

listings %>% filter(section == 'train') %>%
ggplot(mapping = aes(created_ymd)) + 
  geom_bar(aes(fill = interest_level)) + 
  facet_grid(~interest_level) +
  ylab("count of listings")

dates_train <- listings %>% filter(section == 'train') %>%
  select(created_ymd) %>%
  unique() %>%
  arrange(created_ymd)
dates_test <- listings %>% filter(section == 'test') %>%
  select(created_ymd) %>%
  unique() %>%
  arrange(created_ymd)
identical(dates_train,dates_test)
[1] TRUE

The date range of the training and testing data are exactly the same. It would be nice to have an out of time sample to test models on. Testing them on a test set from the same time span may lead to overoptimistic results. Maybe Renthop had reasons for this.

The time frame of the data is from April 1st to June 29th. There doesn’t seem to be a long term trend, but there might be some weekly or hourly trends. Let’s look and find out.

Listing DOW Trend

listings %>% filter(section == 'train') %>%
ggplot(mapping = aes(created_dow)) + 
  geom_bar(aes(fill = interest_level)) + 
  facet_grid(~interest_level) +
  ylab("count of listings") + 
  ggtitle("Distribution of Listings by Day of Week")

Sunday is the first day of the week. We can see that most listings are entered on Wednesday. This is the middle of the workweek for agents.

Listing Hourly Trend

listings %>% filter(section == 'train') %>%
ggplot(mapping = aes(created_h)) + 
  geom_bar(aes(fill = interest_level)) + 
  facet_grid(~interest_level) +
  ylab("count of listings") + 
  ggtitle("Distribution of Listings by Hour of Day")

Most listings were created between midnight and 6:00 in the morning. Since most people sleep at that time, there must be a timezone issue.

tz(listings$created)
[1] "UTC"

So, this data appears to be set to UTC, yet since it’s just a character and not set to any sort of date time object, I would guess that UTC is just the default for the tz function. We know this is for NYC, and most of the listings were probably created by people in NYC. This being the case something funny is going on. I don’t believe most people would create a listing so early in the morning.

View Spatial Distribution

listings %>% filter(section == 'train') %>%
ggplot(aes(x=longitude, y=latitude)) +
geom_point(aes(color = interest_level), size = 0.8, alpha = 0.3) + 
borders("county") +
coord_map(xlim = c(-74.20, -73.70), ylim = c(40.50, 40.95)) + 
ggtitle("Interest Level by Lat/Long")

This is a quick and dirty way to get a map displayed on ggplot2. I’m not too concerned about coordinates that seem to be in the water.

Initial indications show that the outlying areas (outside of Manhattan) have a higher occurrence of high interest listings. They are probably cheaper. Let’s take a closer look at Manhattan to see if we can see anything that may be obscurred at a distance.

listings %>% filter(section == 'train') %>%
ggplot(aes(x=longitude, y=latitude)) +
geom_point(aes(color = interest_level), size = 0.8, alpha = 0.3) + 
borders("county") +
coord_map(xlim = c(-74.05, -73.9 ), ylim = c(40.70, 40.85)) + 
ggtitle("Interest Level (Manhattan) by Lat/Long")

There aren’t any discernible patterns here either. There should be blank spots for central park as well as bodies of water. This lat/long data has some errors. We may need to constrain it to some extent so that it will always be a reasonable value.

Most Common Listed Features

features_low <- data %>% filter(interest_level == "low") %>% 
  select(features) %>% 
  unlist() %>% 
  as_tibble() %>%
  group_by(value) %>%
  summarize(feature_count = n()) %>%
  arrange(desc(feature_count))
wordcloud(features_low$value, features_low$feature_count, scale=c(3,.2), min.freq = 30)

features_medium <- data %>% filter(interest_level == "medium") %>% 
  select(features) %>% 
  unlist() %>% 
  as_tibble() %>%
  group_by(value) %>%
  summarize(feature_count = n()) %>%
  arrange(desc(feature_count))
wordcloud(features_medium$value, features_medium$feature_count, scale=c(3,.2), min.freq = 30)

features_high <- data %>% filter(interest_level == "high") %>% 
  select(features) %>% 
  unlist() %>% 
  as_tibble() %>%
  group_by(value) %>%
  summarize(feature_count = n()) %>%
  arrange(desc(feature_count))
wordcloud(features_high$value, features_high$feature_count, scale=c(3,.2), min.freq = 30)

The results all seem fairly similar. Hardwood floors are popular. So are pets. “Doorman” isn’t mentioned as much in the high interest listings. That probably has a relationship to price.

Find and replace obviously invalid outliers

There were a lot of outliers for price. Although, they were much higher than most, many of them were still possible given that most of the listings were in Manhattan. The high prices will effectively be taken care of later by log transforms. There may be other outliers, which simply can’t be true. Let’s look for them and replace them with reasonable values.

listings %>% 
  group_by(price) %>%
  summarize(listings_count = n()) %>%
  ggplot(aes(price, listings_count)) +
  geom_point() + 
  coord_cartesian(xlim = c(0, 50000))

Nothing obvious stands out on price. I wouldn’t pay some of these prices, but there may be some people.

What about Latitude and longitude? This is supposed to be for NYC. We know from the “View Spatial Distribution” section that the longitude and latitude should be approximately within this box; longitude = c(-74.20, -73.70), latitude = c(40.50, 40.95). So, anything outside this box doesn’t make sense.

unlikely_geo <- listings %>% filter(longitude < -74.20 | longitude > -73.70 | latitude < 40.50 | latitude > 40.95)
ggplot(unlikely_geo, aes(longitude, latitude)) + 
  geom_point() + 
  geom_rect(xmin = -74.20 , xmax = -73.70,   ymin = 40.50, ymax = 40.95,   fill = "red")

Upon inspection, most of the coordinates of these listings are reasonable. They aren’t in NYC itself, but are in the greater metropolitan area. The yellow box above shows the target area. Let’s only filter out only those which are clearly wrong.

long_range <- abs(abs(-74.20) - abs(-73.70))
lat_range <- abs(abs(40.50) - abs(40.95))
very_unlikely_geo <- listings %>% filter(longitude < -74.20 - (10 * long_range) | longitude > -73.70 + (10 * long_range) | latitude < 40.50 - (10 * lat_range) | latitude > 40.95 + (10 * lat_range))
ggplot(very_unlikely_geo, aes(longitude, latitude)) + 
  geom_point() + 
  geom_rect(xmin = -74.20 , xmax = -73.70,   ymin = 40.50, ymax = 40.95,   fill = "red")

The black points above are for the 52 listings with unreasonable coordinates. Their latitude and longitude values will be changed to the median respectively.

lat_median <- median(listings$latitude)
long_median <- median(listings$longitude)
listings %>% filter(listing_id %in% very_unlikely_geo$listing_id) %>%
  mutate(latitude = lat_median,
         longitude = long_median)

Save the “listings” object for use in the next section.

saveRDS(listings, file = "../data/objects/listings.rds")
LS0tCnRpdGxlOiAnVHdvIFNpZ21hIENvbm5lY3Q6IFJlbnRhbCBMaXN0aW5nIElucXVpcmllcyAtIEVEQScKYXV0aG9yOiAiTGF0ZXJhbCBBbmFseXRpY3MiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIG9mIFJlbnRob3AgZGF0YS4KVGhpcyBpcyB0aGUgaW5pdGlhbCBleHBsb3JhdG9yeSBzZWN0aW9uIG9mIHRoZSBwcm9qZWN0LgpJdCBhbHNvIGNyZWF0ZXMgc29tZSBzaW1wbGUgZmVhdHVyZXMuCgojIyBSZW1vdmUgYWxsIG9iamVjdHMKYGBge3J9CnJtKGxpc3QgPSBscygpKQpgYGAKCgojIyBMb2FkIGxpYnJhcmllcwpgYGB7ciBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyBDbGVhciBhbnkgcHJldmlvdXNseSBsb2FkZWQgcGFja2FnZXMKcGtnc18xID0gbmFtZXMoc2Vzc2lvbkluZm8oKSRvdGhlclBrZ3MpCnBrZ3NfMiA9IHBhc3RlKCdwYWNrYWdlOicsIHBrZ3NfMSwgc2VwID0gIiIpCmludmlzaWJsZShpZiAobGVuZ3RoKHBrZ3NfMSkgPiAwKXsKICBsYXBwbHkocGtnc18yLCBkZXRhY2gsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSwgdW5sb2FkID0gVFJVRSkKfSkKCmxpYnJhcnkoInRpZHl2ZXJzZSIpCmxpYnJhcnkoInJqc29uIikKbGlicmFyeSgicm1hcmtkb3duIikKbGlicmFyeSgibHVicmlkYXRlIikKbGlicmFyeSgiZ2dwbG90MiIpCmxpYnJhcnkoIm1hcHMiKQpsaWJyYXJ5KCJtYXBwcm9qIikKbGlicmFyeSgiUkNvbG9yQnJld2VyIikKbGlicmFyeSgid29yZGNsb3VkIikKYGBgCiMjIExvYWQgZGF0YQpTYXZlIGFzIGFuIFJEUyBmaWxlLCBzbyB0aGF0IHdlIGRvbid0IG5lZWQgdG8gY29uc3RhbnRseSByZWxvYWQgdGhpcyBkYXRhIGZvciBvdGhlciBzY3JpcHRzLgpgYGB7cn0KCmRhdGFfdHJhaW4gPC0gcmpzb246OmZyb21KU09OKGZpbGU9ICIuLi9kYXRhL3RyYWluLmpzb24iICkKZGF0YV90ZXN0IDwtIHJqc29uOjpmcm9tSlNPTihmaWxlPSAiLi4vZGF0YS90ZXN0Lmpzb24iICkKCiMgdW5saXN0IGV2ZXJ5IHZhcmlhYmxlIGV4Y2VwdCBgcGhvdG9zYCBhbmQgYGZlYXR1cmVzYCBhbmQgY29udmVydCB0byB0aWJibGUKdmFyc190cmFpbiA8LSBzZXRkaWZmKG5hbWVzKGRhdGFfdHJhaW4pLCBjKCJwaG90b3MiLCAiZmVhdHVyZXMiKSkKdmFyc190ZXN0IDwtIHNldGRpZmYobmFtZXMoZGF0YV90ZXN0KSwgYygicGhvdG9zIiwiZmVhdHVyZXMiKSkKCmRhdGFfdHJhaW4gPC0gbWFwX2F0KGRhdGFfdHJhaW4sIHZhcnNfdHJhaW4sIHVubGlzdCkgJT4lIHRpYmJsZTo6YXNfdGliYmxlKC4pICU+JQogIG11dGF0ZShzZWN0aW9uID0gJ3RyYWluJykKCmRhdGFfdGVzdCA8LSBtYXBfYXQoZGF0YV90ZXN0LCB2YXJzX3Rlc3QsIHVubGlzdCkgJT4lIHRpYmJsZTo6YXNfdGliYmxlKC4pICU+JQogIG11dGF0ZShpbnRlcmVzdF9sZXZlbCA9ICdub25lJywKICAgICAgICAgc2VjdGlvbiA9ICd0ZXN0JykKCmRhdGEgPC0gcmJpbmQoZGF0YV90cmFpbixkYXRhX3Rlc3QpCgpzYXZlUkRTKGRhdGEsIGZpbGUgPSAiLi4vZGF0YS9vYmplY3RzL2RhdGEucmRzIikKCmBgYAoKIyMgVmlldyB0aGUgZGF0YS4KYGBge3J9CmRhdGFbMTozLF0KCmBgYApUaGVyZSBpcyBtb3JlIHRoYW4gb25lIGZlYXR1cmUgb3IgcGhvdG8gZm9yIG1vc3Qgb2YgdGhlIGxpc3RpbmdzLgoKIyMgQ2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzCmBgYHtyfQoKIyMgVGhlcmUgYXJlbid0IGFueSBtaXNzaW5nIHZhbHVlcyBpbiBib3RoIHRyYWluIGFuZCB0ZXN0LgoKcHJpbnQoZGF0YV9uYSA8LSBkYXRhICU+JSBzdW1tYXJpc2VfZWFjaChmdW5zKHN1bShpcy5uYSguKSkpKSkKCgpgYGAKCgoKIyMgRXhwbG9yYXRpb24gLyBTaW1wbGUgRmVhdHVyZXMKIyMjIENoZWNrIEhlYWRlcgpgYGB7cn0KbGlzdGluZ3MgPC0gZGF0YSAlPiUgCiAgc2VsZWN0KC1mZWF0dXJlcywgLSBwaG90b3MpCmhlYWQobGlzdGluZ3MpCmBgYAoKIyMjIENoZWNrIGNvdW50IG9mIGxpc3RpbmdzIGluIGRpZmZlcmVudCBpbnRlcmVzdCBsZXZlbHMKYGBge3J9Cmxpc3RpbmdzICU+JSBmaWx0ZXIoc2VjdGlvbiA9PSAidHJhaW4iKSAlPiUgCiAgZ3JvdXBfYnkoaW50ZXJlc3RfbGV2ZWwpICU+JSAKICBjb3VudCgpCmBgYAoKUmVudGhvcCBkaWRuJ3Qgc3BlY2lmeSB3aGF0IG1ldGhvZG9sb2d5IHRoZXkgdXNlZCB0byBsYWJlbCBlYWNoIGxpc3RpbmcgYW1vbmcgaGlnaCwgbWVkaXVtLCBhbmQgbG93LiAgWWV0LCB0aGV5IGRpZG4ndCBkbyBzbyBlcXVhbGx5LiBUaGVyZSBhcmUgbWFueSBtb3JlIGxvdyB0aGFuIG1lZGl1bSwgYW5kIG1vcmUgbWVkaXVtIHRoYW4gaGlnaC4gVGhpcyBkYXRhc2V0IGlzIHVuYmFsYW5jZWQuCgoKIyMjQ3JlYXRlIEZlYXR1cmVzCmBgYHtyfQojQ291bnRzIGhvdyBtYW55IHBob3RvcyBlYWNoIGxpc3RpbmcgaGFzCnBob3Rvc19jb3VudCA8LSBtYXBfaW50KGRhdGFbWydwaG90b3MnXV0sIGZ1bmN0aW9uKHgpIGxlbmd0aCh1bmxpc3QoeCkpKQpwaG90b3NfY291bnQgPC0gdGliYmxlOjphc190aWJibGUocGhvdG9zX2NvdW50KQpsaXN0aW5ncyRwaG90b3NfY291bnQgPC0gcGhvdG9zX2NvdW50JHZhbHVlCmxpc3RpbmdzICU+JSByZW5hbWUodmFsdWUgPSBwaG90b3NfY291bnQpCgpmZWF0dXJlX2NvdW50IDwtIG1hcF9pbnQoZGF0YVtbJ2ZlYXR1cmVzJ11dLCBmdW5jdGlvbih4KSBsZW5ndGgodW5saXN0KHgpKSkKZmVhdHVyZV9jb3VudCA8LSB0aWJibGU6OmFzX3RpYmJsZShmZWF0dXJlX2NvdW50KQpsaXN0aW5ncyRmZWF0dXJlX2NvdW50IDwtIGZlYXR1cmVfY291bnQkdmFsdWUKbGlzdGluZ3MgJT4lIHJlbmFtZSh2YWx1ZSA9IGZlYXR1cmVfY291bnQpCgojUm9vbSBDb3VudApsaXN0aW5ncyA8LSBtdXRhdGUobGlzdGluZ3MsCiAgICAgICAgICAgICAgICAgICBkYXRlc19mb3JtYXR0ZWQgPSB5bWRfaG1zKGxpc3RpbmdzJGNyZWF0ZWQpLAogICAgICAgICAgICAgICAgICAgdG90YWxfcm9vbXMgPSBiZWRyb29tcyArIGJhdGhyb29tcyArIDEsCiAgICAgICAgICAgICAgICAgICBiZWRzX2JhdGhfcmF0aW8gPSBpZmVsc2UoYmF0aHJvb21zID4gMCwgYmVkcm9vbXMgLyBiYXRocm9vbXMsIE5BKSwKICAgICAgICAgICAgICAgICAgICMgemVybyBiZWRyb29tcyBhbmQgYmF0aHJvb21zIG11c3QgYmUgYW4gZWZmaWNpZW5jeSBhcGFydG1lbnQKICAgICAgICAgICAgICAgICAgIHByaWNlX3Jvb21fcmF0aW8gPSBpZmVsc2UodG90YWxfcm9vbXMgPjAgLCBwcmljZSAvIHRvdGFsX3Jvb21zLCBwcmljZSksCiAgICAgICAgICAgICAgICAgICBwcmljZV9iYXRoX3JhdGlvID0gaWZlbHNlKCBiYXRocm9vbXMgPiAwLCBwcmljZSAvIGJhdGhyb29tcywgTkEpLAogICAgICAgICAgICAgICAgICAgcHJpY2VfcGhvdG9zX3JhdGlvID0gaWZlbHNlKHBob3Rvc19jb3VudCA+IDAsIHByaWNlIC8gcGhvdG9zX2NvdW50LCBOQSksCiAgICAgICAgICAgICAgICAgICBwcmljZV9mZWF0dXJlX3JhdGlvID0gaWZlbHNlKGZlYXR1cmVfY291bnQgPiAwLCBwcmljZSAvIGZlYXR1cmVfY291bnQsIE5BKSwKICAgICAgICAgICAgICAgICAgIGNyZWF0ZWRfeW1kID0geW1kKGFzLkRhdGUoY3JlYXRlZCkpLAogICAgICAgICAgICAgICAgICAgY3JlYXRlZF9kb3cgPSB3ZGF5KGNyZWF0ZWQpLAogICAgICAgICAgICAgICAgICAgY3JlYXRlZF9oID0gaG91cihjcmVhdGVkKQogICAgICAgICAgICAgICAgICAgKQoKYGBgCiAgCiMjIyBBbmFseXplIHRoZSBpbnRpYWwgZmVhdHVyZXMgdG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIGRhdGEKICAKICAKYGBge3J9CiMgTWFrZSBpbnRlcmVzdF9sZXZlbCBhIGZhY3RvciBzbyB0aGF0IGxhdGVyIG91dHB1dCB3aWxsIGRpc3BsYXkgaW4gbG93LCBtZWRpdW0sIGhpZ2ggb3JkZXIKbGlzdGluZ3MgPC0gbGlzdGluZ3MgJT4lCiAgbXV0YXRlKGludGVyZXN0X2xldmVsID0gZmFjdG9yKGludGVyZXN0X2xldmVsLCBsZXZlbHMgPSBjKCJsb3ciLCAibWVkaXVtIiwgImhpZ2giKSkKICAgICAgICAgKQogIApncm91cF9ieShsaXN0aW5ncywgaW50ZXJlc3RfbGV2ZWwpICU+JQogIGZpbHRlcihzZWN0aW9uID09ICd0cmFpbicpICU+JQogICAgc3VtbWFyaXplKAogICAgICAgICAgICAgIGNvdW50ID0gbigpLAogICAgICAgICAgICAgIG1lYW5fcHJpY2UgPSBtZWFuKHByaWNlKSwKICAgICAgICAgICAgICBtaW5fcHJpY2UgPSBtaW4ocHJpY2UpLAogICAgICAgICAgICAgIG1heF9wcmljZSA9IG1heChwcmljZSksCiAgICAgICAgICAgICAgc3RkZXZfcHJpY2UgPSBzZChwcmljZSksIAogICAgICAgICAgICAgIHFfMjUgPSBxdWFudGlsZShwcmljZSwgLjI1KSwgCiAgICAgICAgICAgICAgbWVkaWFuX3ByaWNlID0gbWVkaWFuKHByaWNlKSwgCiAgICAgICAgICAgICAgcV83NSA9IHF1YW50aWxlKHByaWNlLCAuNzUpLCAKICAgICAgICAgICAgICBxXzk5ID0gcXVhbnRpbGUocHJpY2UsIC45OSksCiAgICAgICAgICAgICAgbWVkaWFuX2JlZHJvb21zID0gbWVkaWFuKGJlZHJvb21zKSwKICAgICAgICAgICAgICBtZWRpYW5fdG90YWxfcm9vbXMgPSBtZWRpYW4odG90YWxfcm9vbXMpLAogICAgICAgICAgICAgIHFfOTlfdG90YWxfcm9vbXMgPSBxdWFudGlsZSh0b3RhbF9yb29tcywgLjk5KSwKICAgICAgICAgICAgICBtZWRpYW5fcGhvdG9fY291bnQgPSBtZWRpYW4ocGhvdG9zX2NvdW50KSwKICAgICAgICAgICAgICBtZWRpYW5fZmVhdHVyZXMgPSBtZWRpYW4oZmVhdHVyZV9jb3VudCkKICAgICAgICAgICAgICkKYGBgCgoKVW5zdXJwcmlzaW5nbHksIGxpc3RpbmdzIHdpdGggbG93ZXIgcHJpY2VzIGFuZCBtb3JlIHJvb21zIHJlY2VpdmUgbW9yZSBpbnRlcmVzdC4gIE5vdGUgdGhlIG1heGltdW0gcHJpY2UgZm9yIGEgbG93IGludGVyZXN0IGFwYXJ0bWVudCBpcyAkNCw0OTAsMDAwLiAgRXZlbiBmb3IgTi5ZLkMuIHRoaXMgaXMgaGFyZCB0byBiZWxpZXZlLgoKTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmljZSBkaXN0cmlidXRpb24gd2l0aCBhIGJveHBsb3QuCgojIyMgU2ltcGxlIEJveHBsb3QKYGBge3J9CnByaWNlX2JveCA8LSBsaXN0aW5ncyAlPiUKICBmaWx0ZXIoc2VjdGlvbiA9PSAndHJhaW4nKQoKZ2dwbG90KHByaWNlX2JveCwgYWVzKGludGVyZXN0X2xldmVsLCBwcmljZSkpICsgZ2VvbV9ib3hwbG90KHZhcndpZHRoID0gVFJVRSkKCmBgYApBcyBleHBlY3RlZCBhIGZldyBleHRyZW1lIG91dGxpZXJzIGRpc3RvcnQgdGhlIHJlc3VsdHMuIFdlIG5lZWQgdG8gbGltaXQgdGhlIHkgYXhpcyByYW5nZSBpbiBnZ3Bsb3QyLgoKYGBge3J9CgpnZ3Bsb3QocHJpY2VfYm94LCBhZXMoaW50ZXJlc3RfbGV2ZWwsIHByaWNlKSkgKyBnZW9tX2JveHBsb3QodmFyd2lkdGggPSBUUlVFKSArIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAyMDAwMCkpICsgZ2d0aXRsZSgiSW50ZXJlc3QgTGV2ZWwgU3ByZWFkIikKCgpgYGAKCldlIGNhbiBzZWUgdGhlIHRob3NlIGxpc3RpbmdzIHdpdGggbG93IGludGVyZXN0IGFyZSBoaWdoZXIgaW4gZ2VuZXJhbCwgYnV0IGFsc28gaGF2ZSBhIGxhcmdlIG51bWJlciBvZiBleHBlbnNpdmUgb3V0bGllcnMsIHdoaWNoIGFyZSBhbHNvIHJlYXNvbmFibGUgZm9yIE5ZQy4gVGhlIGhpZ2ggaW50ZXJlc3QgbGlzdGluZ3MgaGF2ZSBhIGxvd2VyIG1lZGlhbiBwcmljZSwgYW5kIGEgbmFycm93ZXIgZGlzdHJpYnV0aW9uLgoKIyMjIERlbnNpdHkgUGxvdApMZXQncyBsb29rIGF0IHByaWNlIGRpc3RyaWJ1dGlvbiB3aXRoIGEgZGVuc2l0eSBwbG90LgoKYGBge3J9CiNQcmljZSBkZW5zaXR5IHBsb3QKbGlzdGluZ3MgJT4lIGZpbHRlcihwcmljZSA8PSBxdWFudGlsZShsaXN0aW5ncyRwcmljZSwgMC45NSksIHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCmdncGxvdChhZXMoeCA9IHByaWNlLCBmaWxsID0gaW50ZXJlc3RfbGV2ZWwpKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgYmlud2lkdGg9IDIwMCwgcG9zaXRpb24gPSAiaWRlbnRpdHkiLCBjb2xvciA9ICJyZWQiLCBzaXplID0gLjA0KSArCiAgICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpICsKICAgIGdndGl0bGUoIlByaWNlIFByb2JhYmlsaXR5IERlbnNpdHkiKQoKYGBgCgpXZSBjYW4gc2VlIGZyb20gdGhpcyBwbG90IHRoYXQgdGhlIGxpc3RpbmdzIHdpdGggaGlnaGVyIGludGVyZXN0IGFyZSBtdWNoIG1vcmUgY29uY2VudHJhdGVkIGFyb3VuZCBsb3cgcHJpY2UuIEFsc28sIGxpa2UgYSBsb3Qgb2YgdHJhbnNhY3Rpb24gZGF0YSBpdCBpcyBub3Qgbm9ybWFsLiAgSXQncyBjbGVhcmx5IHNrZXdlZCB0byB0aGUgcmlnaHQuIFRoaXMgaGFwcGVucyBiZWNhdXNlIG9uIHRoZSBsb3dlciBzaWRlIHRoZSBwcmljZSBkaXN0cmlidXRpb24gaXMgYm91bmQgYnkgemVybywgeWV0IHRoZXJlIGlzIGEgbG90IG9mIHJvb20gdG8gYXNrIGZvciBoaWdoIHByaWNlcy4gTm8gb25lIHdvdWxkIHBheSB5b3UgdG8gbGl2ZSBpbiB0aGVpciBhcGFydG1lbnQuCgojIyMgUHJvcG9ydGlvbiBvZiBCZWRyb29tIGluIEludGVyZXN0IExldmVsCmBgYHtyfQojc3VtbWFyeSB0YWJsZSBmb3IgYmFyIGNoYXJ0ICAKZmlsdGVyKGxpc3RpbmdzLCBzZWN0aW9uID09ICd0cmFpbicpICU+JQpncm91cF9ieShiZWRyb29tcywgaW50ZXJlc3RfbGV2ZWwpICU+JQogIHN1bW1hcml6ZShyb29tX2NvdW50ID0gbigpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBncm91cF9ieShpbnRlcmVzdF9sZXZlbCkgJT4lIAogIG11dGF0ZSh0b3RhbF9iZWRyb29tcyA9IHN1bShyb29tX2NvdW50KSkgJT4lCiAgbXV0YXRlKHAgPSByb29tX2NvdW50L3RvdGFsX2JlZHJvb21zKSAlPiUKICBnZ3Bsb3QoYWVzKGJlZHJvb21zLCBwKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gaW50ZXJlc3RfbGV2ZWwpLCBzdGF0ID0gImlkZW50aXR5IikgICsKICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpICsKICB5bGFiKCJwcm9wb3J0aW9uIG9mIGJlZHJvb21zIGluIGludGVyZXN0IGxldmVsIikKCgpgYGAKClR3byBiZWRyb29tcyBhcmUgdGhlIGhpZ2hlc3QgcHJvcG9ydGlvbiBvZiBhbGwgb2YgdGhlIGludGVyZXN0IGxldmVscy4KCiMjIyBQcmljZXMgYnkgTnVtYmVyIG9mIEJlZHJvb21zCmBgYHtyfQoKZmlsdGVyKGxpc3RpbmdzLCBwcmljZSA8PSBxdWFudGlsZShsaXN0aW5ncyRwcmljZSwgMC45OTkpLCBzZWN0aW9uID09ICd0cmFpbicpICU+JQpnZ3Bsb3QoYWVzKHggPSBiZWRyb29tcywgeSA9IHByaWNlKSkgKwogIGdlb21faml0dGVyKGFlcyhjb2xvciA9IGludGVyZXN0X2xldmVsKSwgYWxwaGEgPSAwLjAyKSArCiAgZmFjZXRfZ3JpZCgufmludGVyZXN0X2xldmVsKQoKYGBgCgpUaGUgcHJpY2Ugb2YgYXBhcnRtZW50cyB3aXRoIG1vcmUgYmVkcm9vbXMgaW5jcmVhc2VzLiAgSXQgaW5jcmVhc2VzIGZhc3RlciBmb3IgbG93IGludGVyZXN0IGFwYXJ0bWVudHMuIEludGVyZXN0IGluIGFuIGFwYXJ0bWVudCBkZWNyZWFzZXMgcXVpY2tseSBhZnRlciB0aGUgcHJpY2UgZ2V0cyB0byBhYm91dCAkOCwwMDAgcGVyIG1vbnRoLgoKIyMjIFByaWNlIHRvIFJvb20gUmF0aW8KYGBge3J9CmZpbHRlcihsaXN0aW5ncywgcHJpY2UgPD0gcXVhbnRpbGUobGlzdGluZ3MkcHJpY2UsIDAuOTUpLCBzZWN0aW9uID09ICd0cmFpbicpICU+JQpnZ3Bsb3QoYWVzKHggPSB0b3RhbF9yb29tcywgeSA9IHByaWNlX3Jvb21fcmF0aW8pKSArCiAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yID0gaW50ZXJlc3RfbGV2ZWwpLCBhbHBoYSA9IDAuMDIpICsKICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpCmBgYAoKIyMjIEZlYXR1cmUgQ291bnQgRGVuc2l0eQpgYGB7cn0KCiNmZWF0dXJlcyBieSBpbnRlcmVzdApmaWx0ZXIobGlzdGluZ3MsIHByaWNlIDw9IHF1YW50aWxlKGxpc3RpbmdzJHByaWNlLCAxLjAwKSwgc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKZ2dwbG90KGFlcyh4ID0gZmVhdHVyZV9jb3VudCkpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uLCBmaWxsID0gaW50ZXJlc3RfbGV2ZWwpLCAKICAgICAgICAgICAgICAgICBiaW5zID0gNDAsIHBvc2l0aW9uID0gImlkZW50aXR5IiwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4wNykgKwogIGZhY2V0X2dyaWQoLn5pbnRlcmVzdF9sZXZlbCkgKwogIGd1aWRlcyhjb2xvciA9IEZBTFNFKQpgYGAKClRoZSBkZW5zaXR5IG9mIGZlYXR1cmUgY291bnQgZG9lc24ndCBzZWVtIHRvIHZhcnkgZ3JlYXRseSBieSBpbnRlcmVzdCBsZXZlbC4gTW9zdCBsaXN0aW5ncyBoYXZlIGFib3V0IDUgZmVhdHVyZXMuIFN1cnByaXNpbmdseSwgdGhlIGhpZ2ggaW50ZXJlc3QgbGV2ZWwgbGlzdGluZ3MgaGF2ZSB0aGUgaGlnaGVzdCBwcm9wb3J0aW9uIG9mIHplcm8gbGlzdGVkIGZlYXR1cmUgbGlzdGluZ3MuIFRoZXkgbXVzdCBoYXZlIHNvbWV0aGluZyBvYnZpb3VzIHRvIG9mZmVyLCBzdWNoIHRoYXQgdGhlIGxpc3RlciBkb2Vucyd0IHRoaW5rIHRoZXJlIGlzIGEgbmVlZCB0byBsaXN0IGZlYXR1cmVzLgoKIyMjIFByaWNlIGJ5IE51bWJlciBvZiBGZWF0dXJlcwpgYGB7cn0KI3ByaWNlIGJ5IG51bWJlciBvZiBmZWF0dXJlcwpmaWx0ZXIobGlzdGluZ3MsIHByaWNlIDw9IHF1YW50aWxlKGxpc3RpbmdzJHByaWNlLCAwLjk5KSwgc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKZ2dwbG90KGFlcyh4ID0gZmVhdHVyZV9jb3VudCwgeSA9IHByaWNlKSkgKwogIGdlb21faml0dGVyKGFlcyhjb2xvciA9IGludGVyZXN0X2xldmVsKSwgYWxwaGEgPSAuMTApICsKICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpICsKICBnZ3RpdGxlKCJWYXJpYXRpb24gb2YgUHJpY2UgYXQgZGlmZmVyZW50IGZlYXR1cmUgY291bnQgYW5kIGludGVyZXN0IGxldmVscyIpICsKZ2VvbV9zbW9vdGgoKQoKCgpgYGAKCkV4Y2x1ZGluZyBvdXRsaWVycyB0aGVyZSBpcyBhIGNsZWFyIHBvc2l0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGZlYXR1cmVfY291bnQgYW5kIHByaWNlLiAgSWYgeW91IHdhbnQgbW9yZSBtb25leSB0aGVuIHlvdSBuZWVkIHRvIGV4cGxhaW4gd2h5IHlvdXIgYXBhcnRtZW50IGlzIHdvcnRoIGl0LiBUaGVyZSBhcmUgYWxzbyBtYW55IGhpZ2ggcHJpY2VkIGFwYXJ0bWVudHMgd2l0aCBhIGxvdyBpbnRlcmVzdCBsZXZlbCBhbmQgYSBzbWFsbCBudW1iZXIgb2YgZmVhdHVyZXMgbGlzdGVkLiAgVGhhdCBtYWtlcyBzZW5zZS4gV29yayBoYXJkZXI6KQoKCiMjIyBQaG90byBDb3VudCBEZW5zaXR5CmBgYHtyfQoKI3Bob3RvcyBieSBpbnRlcmVzdApmaWx0ZXIobGlzdGluZ3MsIHByaWNlIDw9IHF1YW50aWxlKGxpc3RpbmdzJHByaWNlLCAxLjAwKSwgc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKZ2dwbG90KGFlcyh4ID0gcGhvdG9zX2NvdW50KSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSBpbnRlcmVzdF9sZXZlbCksIAogICAgICAgICAgICAgICAgIGJpbnMgPSA0MCwgcG9zaXRpb24gPSAiaWRlbnRpdHkiLCBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjA3KSArCiAgZmFjZXRfZ3JpZCgufmludGVyZXN0X2xldmVsKSArCiAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpCmBgYAoKVGhlIGRlbnNpdHkgb2YgcGhvdG8gY291bnQgZG9lc24ndCBzZWVtIHRvIHZhcnkgZ3JlYXRseSBieSBpbnRlcmVzdCBsZXZlbC4gTW9zdCBsaXN0aW5ncyBoYXZlIGFib3V0IDUgcGhvdG9zCgojIyMgUHJpY2UgYnkgTnVtYmVyIG9mIFBob3RvcwpgYGB7cn0KI3ByaWNlIGJ5IG51bWJlciBvZiBwaG90b3MKZmlsdGVyKGxpc3RpbmdzLCBwcmljZSA8PSBxdWFudGlsZShsaXN0aW5ncyRwcmljZSwgMC45OSksIHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCmdncGxvdChhZXMoeCA9IHBob3Rvc19jb3VudCwgeSA9IHByaWNlKSkgKwogIGdlb21faml0dGVyKGFlcyhjb2xvciA9IGludGVyZXN0X2xldmVsKSwgYWxwaGEgPSAuMTApICsKICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpICsKICBnZ3RpdGxlKCJWYXJpYXRpb24gb2YgUHJpY2UgYXQgZGlmZmVyZW50IHBob3RvcyBjb3VudCBhbmQgaW50ZXJlc3QgbGV2ZWxzIikgKwpnZW9tX3Ntb290aCgpCgoKCmBgYApGb3IgdGhlIGxvdyBpbnRlcmVzdCBsaXN0aW5ncyB0aGVyZSBpcyBhIGNsZWFyIHBvc2l0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgcGhvdHMgaW4gdGhlIGxpc3RpbmcgYW5kIHByaWNlLiBUaGUgcGVvcGxlIHdobyBjcmVhdGUgdGhlIGxpc3RpbmcgbXVzdCByZWFsaXplIHRoZXkgbmVlZCB0byB3b3JrIGhhcmRlci4gVGhlIHJlbGF0aW9uc2hpcCBpcyBsZXNzIGNsZWFyIGZvciB0aGUgbWVkaXVtIGFuZCBsb3cgaW50ZXJlc3QgbGlzdGluZ3MsIGFsdGhvdWdoIGl0J3Mgc3RpbGwgdGhlcmUuIEl0IGlzIGNsZWFyIGhvd2V2ZXIsIHRoYXQgdGhlIGZpcnN0IDEwIHBob3RvcyBhcmUgc3Ryb25nbHkgYXNzb2NpYXRlZCB3aXRoIGEgbGFyZ2VyIHByaWNlLiBJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBrbm93IGhvdyBsb25nIGVhY2ggbGlzdGluZyB3YXMgb24gdGhlIG1hcmtldC4gVGhlbiB3ZSBjb3VsZCBoYXZlIGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgaWYgdGhlIHByaWNlcyBhc2tlZCB3ZXJlIHJlYXNvbmFibGUgbWFya2V0IHByaWNlcyBvciBub3QuIEFyZSB0aGUgbGlzdGVycyBhYmxlIHRvIGFzayBhIGhpZ2hlciBwcmljZSBiZWNhdXNlIHRoZXkgc3BlbnQgbW9yZSB0aW1lIHVwbG9hZGluZyBwaG90b3MsIG9yIGlzIHRoZSBwcmljZSBhbHJlYWR5IGhpZ2hlciBhbmQgdGhleSBuZWVkIHRvIHNob3cgd2h5IHRoYXQgaXM/CgoKIyMjIExpc3RpbmdzIENyZWF0aW9uIERhdGUKYGBge3J9Cmxpc3RpbmdzICU+JSBmaWx0ZXIoc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKZ2dwbG90KG1hcHBpbmcgPSBhZXMoY3JlYXRlZF95bWQpKSArIAogIGdlb21fYmFyKGFlcyhmaWxsID0gaW50ZXJlc3RfbGV2ZWwpKSArIAogIGZhY2V0X2dyaWQofmludGVyZXN0X2xldmVsKSArCiAgeWxhYigiY291bnQgb2YgbGlzdGluZ3MiKQoKYGBgCmBgYHtyfQpkYXRlc190cmFpbiA8LSBsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCiAgc2VsZWN0KGNyZWF0ZWRfeW1kKSAlPiUKICB1bmlxdWUoKSAlPiUKICBhcnJhbmdlKGNyZWF0ZWRfeW1kKQoKZGF0ZXNfdGVzdCA8LSBsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3Rlc3QnKSAlPiUKICBzZWxlY3QoY3JlYXRlZF95bWQpICU+JQogIHVuaXF1ZSgpICU+JQogIGFycmFuZ2UoY3JlYXRlZF95bWQpCgppZGVudGljYWwoZGF0ZXNfdHJhaW4sZGF0ZXNfdGVzdCkKCmBgYApUaGUgZGF0ZSByYW5nZSBvZiB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBhcmUgZXhhY3RseSB0aGUgc2FtZS4gSXQgd291bGQgYmUgbmljZSB0byBoYXZlIGFuIG91dCBvZiB0aW1lIHNhbXBsZSB0byB0ZXN0IG1vZGVscyBvbi4gVGVzdGluZyB0aGVtIG9uIGEgdGVzdCBzZXQgZnJvbSB0aGUgc2FtZSB0aW1lIHNwYW4gbWF5IGxlYWQgdG8gb3Zlcm9wdGltaXN0aWMgcmVzdWx0cy4gTWF5YmUgUmVudGhvcCBoYWQgcmVhc29ucyBmb3IgdGhpcy4KClRoZSB0aW1lIGZyYW1lIG9mIHRoZSBkYXRhIGlzIGZyb20gQXByaWwgMXN0IHRvIEp1bmUgMjl0aC4gVGhlcmUgZG9lc24ndCBzZWVtIHRvIGJlIGEgbG9uZyB0ZXJtIHRyZW5kLCBidXQgdGhlcmUgbWlnaHQgYmUgc29tZSB3ZWVrbHkgb3IgaG91cmx5IHRyZW5kcy4gIExldCdzIGxvb2sgYW5kIGZpbmQgb3V0LgoKIyMjIExpc3RpbmcgRE9XIFRyZW5kCmBgYHtyfQpsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCmdncGxvdChtYXBwaW5nID0gYWVzKGNyZWF0ZWRfZG93KSkgKyAKICBnZW9tX2JhcihhZXMoZmlsbCA9IGludGVyZXN0X2xldmVsKSkgKyAKICBmYWNldF9ncmlkKH5pbnRlcmVzdF9sZXZlbCkgKwogIHlsYWIoImNvdW50IG9mIGxpc3RpbmdzIikgKyAKICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgTGlzdGluZ3MgYnkgRGF5IG9mIFdlZWsiKQpgYGAKU3VuZGF5IGlzIHRoZSBmaXJzdCBkYXkgb2YgdGhlIHdlZWsuICBXZSBjYW4gc2VlIHRoYXQgbW9zdCBsaXN0aW5ncyBhcmUgZW50ZXJlZCBvbiBXZWRuZXNkYXkuIFRoaXMgaXMgdGhlIG1pZGRsZSBvZiB0aGUgd29ya3dlZWsgZm9yIGFnZW50cy4KCiMjIyBMaXN0aW5nIEhvdXJseSBUcmVuZApgYGB7cn0KbGlzdGluZ3MgJT4lIGZpbHRlcihzZWN0aW9uID09ICd0cmFpbicpICU+JQpnZ3Bsb3QobWFwcGluZyA9IGFlcyhjcmVhdGVkX2gpKSArIAogIGdlb21fYmFyKGFlcyhmaWxsID0gaW50ZXJlc3RfbGV2ZWwpKSArIAogIGZhY2V0X2dyaWQofmludGVyZXN0X2xldmVsKSArCiAgeWxhYigiY291bnQgb2YgbGlzdGluZ3MiKSArIAogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBMaXN0aW5ncyBieSBIb3VyIG9mIERheSIpCgpgYGAKTW9zdCBsaXN0aW5ncyB3ZXJlIGNyZWF0ZWQgYmV0d2VlbiBtaWRuaWdodCBhbmQgNjowMCBpbiB0aGUgbW9ybmluZy4gU2luY2UgbW9zdCBwZW9wbGUgc2xlZXAgYXQgdGhhdCB0aW1lLCB0aGVyZSBtdXN0IGJlIGEgdGltZXpvbmUgaXNzdWUuCmBgYHtyfQp0eihsaXN0aW5ncyRjcmVhdGVkKQpgYGAKClNvLCB0aGlzIGRhdGEgYXBwZWFycyB0byBiZSBzZXQgdG8gVVRDLCB5ZXQgc2luY2UgaXQncyBqdXN0IGEgY2hhcmFjdGVyIGFuZCBub3Qgc2V0IHRvIGFueSBzb3J0IG9mIGRhdGUgdGltZSBvYmplY3QsIEkgd291bGQgZ3Vlc3MgdGhhdCBVVEMgaXMganVzdCB0aGUgZGVmYXVsdCBmb3IgdGhlIHR6IGZ1bmN0aW9uLiAgV2Uga25vdyB0aGlzIGlzIGZvciBOWUMsIGFuZCBtb3N0IG9mIHRoZSBsaXN0aW5ncyB3ZXJlIHByb2JhYmx5IGNyZWF0ZWQgYnkgcGVvcGxlIGluIE5ZQy4gVGhpcyBiZWluZyB0aGUgY2FzZSBzb21ldGhpbmcgZnVubnkgaXMgZ29pbmcgb24uICBJIGRvbid0IGJlbGlldmUgbW9zdCBwZW9wbGUgd291bGQgY3JlYXRlIGEgbGlzdGluZyBzbyBlYXJseSBpbiB0aGUgbW9ybmluZy4KCiMjIyBWaWV3IFNwYXRpYWwgRGlzdHJpYnV0aW9uCmBgYHtyfQpsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCmdncGxvdChhZXMoeD1sb25naXR1ZGUsIHk9bGF0aXR1ZGUpKSArCmdlb21fcG9pbnQoYWVzKGNvbG9yID0gaW50ZXJlc3RfbGV2ZWwpLCBzaXplID0gMC44LCBhbHBoYSA9IDAuMykgKyAKYm9yZGVycygiY291bnR5IikgKwpjb29yZF9tYXAoeGxpbSA9IGMoLTc0LjIwLCAtNzMuNzApLCB5bGltID0gYyg0MC41MCwgNDAuOTUpKSArIApnZ3RpdGxlKCJJbnRlcmVzdCBMZXZlbCBieSBMYXQvTG9uZyIpCgpgYGAKClRoaXMgaXMgYSBxdWljayBhbmQgZGlydHkgd2F5IHRvIGdldCBhIG1hcCBkaXNwbGF5ZWQgb24gZ2dwbG90Mi4gSSdtIG5vdCB0b28gY29uY2VybmVkIGFib3V0IGNvb3JkaW5hdGVzIHRoYXQgc2VlbSB0byBiZSBpbiB0aGUgd2F0ZXIuCgpJbml0aWFsIGluZGljYXRpb25zIHNob3cgdGhhdCB0aGUgb3V0bHlpbmcgYXJlYXMgKG91dHNpZGUgb2YgTWFuaGF0dGFuKSBoYXZlIGEgaGlnaGVyIG9jY3VycmVuY2Ugb2YgaGlnaCBpbnRlcmVzdCBsaXN0aW5ncy4gVGhleSBhcmUgcHJvYmFibHkgY2hlYXBlci4gTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IE1hbmhhdHRhbiB0byBzZWUgaWYgd2UgY2FuIHNlZSBhbnl0aGluZyB0aGF0IG1heSBiZSBvYnNjdXJyZWQgYXQgYSBkaXN0YW5jZS4KCmBgYHtyfQpsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCmdncGxvdChhZXMoeD1sb25naXR1ZGUsIHk9bGF0aXR1ZGUpKSArCmdlb21fcG9pbnQoYWVzKGNvbG9yID0gaW50ZXJlc3RfbGV2ZWwpLCBzaXplID0gMC44LCBhbHBoYSA9IDAuMykgKyAKYm9yZGVycygiY291bnR5IikgKwpjb29yZF9tYXAoeGxpbSA9IGMoLTc0LjA1LCAtNzMuOSApLCB5bGltID0gYyg0MC43MCwgNDAuODUpKSArIApnZ3RpdGxlKCJJbnRlcmVzdCBMZXZlbCAoTWFuaGF0dGFuKSBieSBMYXQvTG9uZyIpCmBgYAoKVGhlcmUgYXJlbid0IGFueSBkaXNjZXJuaWJsZSBwYXR0ZXJucyBoZXJlIGVpdGhlci4gVGhlcmUgc2hvdWxkIGJlIGJsYW5rIHNwb3RzIGZvciBjZW50cmFsIHBhcmsgYXMgd2VsbCBhcyBib2RpZXMgb2Ygd2F0ZXIuIFRoaXMgbGF0L2xvbmcgZGF0YSBoYXMgc29tZSBlcnJvcnMuIFdlIG1heSBuZWVkIHRvIGNvbnN0cmFpbiBpdCB0byBzb21lIGV4dGVudCBzbyB0aGF0IGl0IHdpbGwgYWx3YXlzIGJlIGEgcmVhc29uYWJsZSB2YWx1ZS4KCgoKIyMjIFBvcHVsYXIgUHJvcGVydHkgTWFuYWdlcnMKSXQgY291bGQgYmUgdGhhdCBjZXJ0YWluIHByb3BlcnR5IG1hbmFnZXJzIGFyZSB2ZXJ5IHBvcHVsYXIuIElmIHNvLCB0aGUgcHJvcGVydHkgbWFuYWdlciBJRCBjb3VsZCBiZSBhbiBpbXBvcnRhbnQgZmVhdHVyZS4KCmBgYHtyfQptYW5hZ2VyX2NvdW50IDwtIGxpc3RpbmdzICU+JSBmaWx0ZXIoc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKICBncm91cF9ieShtYW5hZ2VyX2lkKSAlPiUKICBzdW1tYXJpc2Uobl9tYW5hZ2VyID0gbigpKSAlPiUKICBhcnJhbmdlKG1hbmFnZXJfaWQpCgptYW5hZ2VyX2ludGVyZXN0X2NvdW50IDwtIGxpc3RpbmdzICU+JSBmaWx0ZXIoc2VjdGlvbiA9PSAndHJhaW4nKSAlPiUKICBncm91cF9ieShtYW5hZ2VyX2lkLCBpbnRlcmVzdF9sZXZlbCkgJT4lCiAgc3VtbWFyaXNlKG5fbWFuYWdlcl9pbnRlcmVzdCA9IG4oKSkgJT4lCiAgYXJyYW5nZShtYW5hZ2VyX2lkKQoKaW5uZXJfam9pbihtYW5hZ2VyX2NvdW50LCBtYW5hZ2VyX2ludGVyZXN0X2NvdW50KSAlPiUKICBtdXRhdGUoaW50ZXJlc3RfcGN0ID0gbl9tYW5hZ2VyX2ludGVyZXN0IC8gbl9tYW5hZ2VyKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBuX21hbmFnZXIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiwgZmlsbCA9IGludGVyZXN0X2xldmVsKSwgYmlud2lkdGggPSA1KSArIAogIGZhY2V0X2dyaWQoLn5pbnRlcmVzdF9sZXZlbCkgKyAKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsIDEwMCkpICsgCiAgZ2d0aXRsZSgiVG9wIFByb3BlcnR5IE1hbmFnZXJzIikKYGBgCgpUaGVyIGFyZSBhcmUgbWFueSBtYW5hZ2VycyB3aXRoIDAgdG8gMTAgbGlzdGluZ3MuICBMZXQncyBmaWx0ZXIgb24gdGhvc2Ugd2l0aCBtb3JlIHRoYW4gMTAsIGFuZCBjb25zaWRlciB0aG9zZSBwb3RlbnRpYWwgcG9wdWxhciBtYW5hZ2Vycy4gCgpgYGB7cn0KKG1hbmFnZXJfaW50ZXJlc3RfcGN0IDwtIGlubmVyX2pvaW4obWFuYWdlcl9jb3VudCwgbWFuYWdlcl9pbnRlcmVzdF9jb3VudCkgJT4lCiAgbXV0YXRlKGludGVyZXN0X3BjdCA9IG5fbWFuYWdlcl9pbnRlcmVzdCAvIG5fbWFuYWdlcikgJT4lCiAgZmlsdGVyKGludGVyZXN0X2xldmVsID09ICJoaWdoIiwgbl9tYW5hZ2VyID49IDEwLCBpbnRlcmVzdF9wY3QgPiAwLjUpICU+JQogIGFycmFuZ2UoLWludGVyZXN0X3BjdCkpCgoKYGBgCgpUaGVyZSBhcmUgMTIgcHJvcGVydHkgbWFuYWdlcnMgd2hvIGhhdmUgYXQgbGVhc3QgMTAgbGlzdGluZ3MgYXQgbGVhc3QgNTAlIG9mIHdoaWNoIGhhdmUgYSBoaWdoIGludGVyZXN0IGxldmVsLiBUaGVzZSBtYW5hZ2VycyBoYXZlIGRvbmUgc29tZXRoaW5nIHJpZ2h0LiBBIGZlYXR1cmUgc2hvdWxkIGJlIGNyZWF0ZWQgd2hpY2ggY2FsbHMgIHRoZW0gb3V0LgoKIyMjIFBvcHVsYXIgYnVpbGRpbmdzCmBgYHtyfQpidWlsZGluZ19jb3VudCA8LSBsaXN0aW5ncyAlPiUgZmlsdGVyKHNlY3Rpb24gPT0gJ3RyYWluJykgJT4lCiAgZ3JvdXBfYnkoYnVpbGRpbmdfaWQpICU+JQogIHN1bW1hcmlzZShuX2J1aWxkaW5nID0gbigpKSAlPiUKICBhcnJhbmdlKGJ1aWxkaW5nX2lkKQoKYnVpbGRpbmdfaW50ZXJlc3RfY291bnQgPC0gbGlzdGluZ3MgJT4lIGZpbHRlcihzZWN0aW9uID09ICd0cmFpbicpICU+JQogIGdyb3VwX2J5KGJ1aWxkaW5nX2lkLCBpbnRlcmVzdF9sZXZlbCkgJT4lCiAgc3VtbWFyaXNlKG5fYnVpbGRpbmdfaW50ZXJlc3QgPSBuKCkpICU+JQogIGFycmFuZ2UoYnVpbGRpbmdfaWQpCgppbm5lcl9qb2luKGJ1aWxkaW5nX2NvdW50LCBidWlsZGluZ19pbnRlcmVzdF9jb3VudCkgJT4lCiAgbXV0YXRlKGludGVyZXN0X3BjdCA9IG5fYnVpbGRpbmdfaW50ZXJlc3QgLyBuX2J1aWxkaW5nKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBuX2J1aWxkaW5nKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4sIGZpbGwgPSBpbnRlcmVzdF9sZXZlbCksIGJpbndpZHRoID0gNSkgKyAKICBmYWNldF9ncmlkKC5+aW50ZXJlc3RfbGV2ZWwpICsgCiAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLCAxMDApKSArIAogIGdndGl0bGUoIlRvcCBCdWlsZGluZ3MiKQpgYGAKCkFmdGVyIGFib3V0IDEwIGxpc3RpbmdzIHRoZSBudW1iZXJzIGRlY3JlYXNlLiBMZXQncyBsb29rIGF0IHRob3NlIGJ1aWxkaW5nIHdpdGggYXQgbGVhc3QgMTAgbGlzdGluZ3MgYW5kIGEgaGlnaCBpbnRlcmVzdCBsZXZlbCBhYm92ZSA1MCUuCgpgYGB7cn0KKGJ1aWxkaW5nX2ludGVyZXN0X3BjdCA8LSBpbm5lcl9qb2luKGJ1aWxkaW5nX2NvdW50LCBidWlsZGluZ19pbnRlcmVzdF9jb3VudCkgJT4lCiAgbXV0YXRlKGludGVyZXN0X3BjdCA9IG5fYnVpbGRpbmdfaW50ZXJlc3QgLyBuX2J1aWxkaW5nKSAlPiUKICBmaWx0ZXIoaW50ZXJlc3RfbGV2ZWwgPT0gImhpZ2giLCBuX2J1aWxkaW5nID49IDEwLCBpbnRlcmVzdF9wY3QgPiAwLjUpICU+JQogIGFycmFuZ2UoLWludGVyZXN0X3BjdCkpCmBgYAoKSnVzdCA5IGJ1aWxkaW5ncyBtYWRlIHRoZSBjdXQuICBMZXQncyBzZWUgd2hlcmUgdGhleSBhcmUuCgpgYGB7cn0KYnVpbGRpbmdfbWFwX2RhdGEgPC0gaW5uZXJfam9pbihidWlsZGluZ19pbnRlcmVzdF9wY3QsIGxpc3RpbmdzKQoKCmJ1aWxkaW5nX21hcF9kYXRhICU+JQpnZ3Bsb3QoYWVzKHg9bG9uZ2l0dWRlLCB5PWxhdGl0dWRlKSkgKwpnZW9tX3BvaW50KGFlcyhjb2xvciA9IGludGVyZXN0X2xldmVsKSwgc2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjMpICsgCmJvcmRlcnMoImNvdW50eSIpICsKY29vcmRfbWFwKHhsaW0gPSBjKC03NC4xMCwgLTczLjgwKSwgeWxpbSA9IGMoNDAuNjAsIDQwLjkwKSkgKyAKZ2d0aXRsZSgiUG9wdWxhciBCdWlsZGluZ3MgLSBJbnRlcmVzdCBMZXZlbCBieSBMYXQvTG9uZyIpCmBgYAoKTW9zdCBvZiB0aGUgcG9wdWxhciBidWlsZGluZ3MgYXJlIGluIGxvd2VyIE1hbmhhdHRhbi4gQSBmZWF0dXJlIGNvdWxkIGJlIGNyZWF0ZWQgd2hpY2ggc3BlY2lmaWVzIHRoZSBkaXN0YW5jZSBvZiBhIGdpdmVuIGJ1aWxkaW5nIGZyb20gdGhpcyBhcmVhLgoKIyMjIE1vc3QgQ29tbW9uIExpc3RlZCBGZWF0dXJlcwpgYGB7cn0KZmVhdHVyZXNfbG93IDwtIGRhdGEgJT4lIGZpbHRlcihpbnRlcmVzdF9sZXZlbCA9PSAibG93IikgJT4lIAogIHNlbGVjdChmZWF0dXJlcykgJT4lIAogIHVubGlzdCgpICU+JSAKICBhc190aWJibGUoKSAlPiUKICBncm91cF9ieSh2YWx1ZSkgJT4lCiAgc3VtbWFyaXplKGZlYXR1cmVfY291bnQgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhmZWF0dXJlX2NvdW50KSkKCndvcmRjbG91ZChmZWF0dXJlc19sb3ckdmFsdWUsIGZlYXR1cmVzX2xvdyRmZWF0dXJlX2NvdW50LCBzY2FsZT1jKDMsLjIpLCBtaW4uZnJlcSA9IDMwKQpgYGAKCgpgYGB7cn0KZmVhdHVyZXNfbWVkaXVtIDwtIGRhdGEgJT4lIGZpbHRlcihpbnRlcmVzdF9sZXZlbCA9PSAibWVkaXVtIikgJT4lIAogIHNlbGVjdChmZWF0dXJlcykgJT4lIAogIHVubGlzdCgpICU+JSAKICBhc190aWJibGUoKSAlPiUKICBncm91cF9ieSh2YWx1ZSkgJT4lCiAgc3VtbWFyaXplKGZlYXR1cmVfY291bnQgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhmZWF0dXJlX2NvdW50KSkKCndvcmRjbG91ZChmZWF0dXJlc19tZWRpdW0kdmFsdWUsIGZlYXR1cmVzX21lZGl1bSRmZWF0dXJlX2NvdW50LCBzY2FsZT1jKDMsLjIpLCBtaW4uZnJlcSA9IDMwKQpgYGAKCmBgYHtyfQoKZmVhdHVyZXNfaGlnaCA8LSBkYXRhICU+JSBmaWx0ZXIoaW50ZXJlc3RfbGV2ZWwgPT0gImhpZ2giKSAlPiUgCiAgc2VsZWN0KGZlYXR1cmVzKSAlPiUgCiAgdW5saXN0KCkgJT4lIAogIGFzX3RpYmJsZSgpICU+JQogIGdyb3VwX2J5KHZhbHVlKSAlPiUKICBzdW1tYXJpemUoZmVhdHVyZV9jb3VudCA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKGZlYXR1cmVfY291bnQpKQoKd29yZGNsb3VkKGZlYXR1cmVzX2hpZ2gkdmFsdWUsIGZlYXR1cmVzX2hpZ2gkZmVhdHVyZV9jb3VudCwgc2NhbGU9YygzLC4yKSwgbWluLmZyZXEgPSAzMCkKCmBgYAoKVGhlIHJlc3VsdHMgYWxsIHNlZW0gZmFpcmx5IHNpbWlsYXIuICBIYXJkd29vZCBmbG9vcnMgYXJlIHBvcHVsYXIuIFNvIGFyZSBwZXRzLiAiRG9vcm1hbiIgaXNuJ3QgbWVudGlvbmVkIGFzIG11Y2ggaW4gdGhlIGhpZ2ggaW50ZXJlc3QgbGlzdGluZ3MuIFRoYXQgcHJvYmFibHkgaGFzIGEgcmVsYXRpb25zaGlwIHRvIHByaWNlLgoKCiMjIyBGaW5kIGFuZCByZXBsYWNlIG9idmlvdXNseSBpbnZhbGlkIG91dGxpZXJzClRoZXJlIHdlcmUgYSBsb3Qgb2Ygb3V0bGllcnMgZm9yIHByaWNlLiBBbHRob3VnaCwgdGhleSB3ZXJlIG11Y2ggaGlnaGVyIHRoYW4gbW9zdCwgbWFueSBvZiB0aGVtIHdlcmUgc3RpbGwgcG9zc2libGUgZ2l2ZW4gdGhhdCBtb3N0IG9mIHRoZSBsaXN0aW5ncyB3ZXJlIGluIE1hbmhhdHRhbi4gVGhlIGhpZ2ggcHJpY2VzIHdpbGwgZWZmZWN0aXZlbHkgYmUgdGFrZW4gY2FyZSBvZiBsYXRlciBieSBsb2cgdHJhbnNmb3Jtcy4gVGhlcmUgbWF5IGJlIG90aGVyIG91dGxpZXJzLCB3aGljaCBzaW1wbHkgY2FuJ3QgYmUgdHJ1ZS4gTGV0J3MgbG9vayBmb3IgdGhlbSBhbmQgcmVwbGFjZSB0aGVtIHdpdGggcmVhc29uYWJsZSB2YWx1ZXMuCgpgYGB7cn0KbGlzdGluZ3MgJT4lIAogIGdyb3VwX2J5KHByaWNlKSAlPiUKICBzdW1tYXJpemUobGlzdGluZ3NfY291bnQgPSBuKCkpICU+JQogIGdncGxvdChhZXMocHJpY2UsIGxpc3RpbmdzX2NvdW50KSkgKwogIGdlb21fcG9pbnQoKSArIAogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCA1MDAwMCkpCmBgYAoKTm90aGluZyBvYnZpb3VzIHN0YW5kcyBvdXQgb24gcHJpY2UuIEkgd291bGRuJ3QgcGF5IHNvbWUgb2YgdGhlc2UgcHJpY2VzLCBidXQgdGhlcmUgbWF5IGJlIHNvbWUgcGVvcGxlLgoKV2hhdCBhYm91dCBMYXRpdHVkZSBhbmQgbG9uZ2l0dWRlPwpUaGlzIGlzIHN1cHBvc2VkIHRvIGJlIGZvciBOWUMuIFdlIGtub3cgZnJvbSB0aGUgIlZpZXcgU3BhdGlhbCBEaXN0cmlidXRpb24iIHNlY3Rpb24gdGhhdCB0aGUgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZSBzaG91bGQgYmUgYXBwcm94aW1hdGVseSB3aXRoaW4gdGhpcyBib3g7IGxvbmdpdHVkZSA9IGMoLTc0LjIwLCAtNzMuNzApLCBsYXRpdHVkZSA9IGMoNDAuNTAsIDQwLjk1KS4gU28sIGFueXRoaW5nIG91dHNpZGUgdGhpcyBib3ggZG9lc24ndCBtYWtlIHNlbnNlLgoKYGBge3J9CnVubGlrZWx5X2dlbyA8LSBsaXN0aW5ncyAlPiUgZmlsdGVyKGxvbmdpdHVkZSA8IC03NC4yMCB8IGxvbmdpdHVkZSA+IC03My43MCB8IGxhdGl0dWRlIDwgNDAuNTAgfCBsYXRpdHVkZSA+IDQwLjk1KQoKZ2dwbG90KHVubGlrZWx5X2dlbywgYWVzKGxvbmdpdHVkZSwgbGF0aXR1ZGUpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fcmVjdCh4bWluID0gLTc0LjIwICwgeG1heCA9IC03My43MCwgICB5bWluID0gNDAuNTAsIHltYXggPSA0MC45NSwgICBmaWxsID0gInJlZCIpCgpgYGAKClVwb24gaW5zcGVjdGlvbiwgbW9zdCBvZiB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlc2UgbGlzdGluZ3MgYXJlIHJlYXNvbmFibGUuIFRoZXkgYXJlbid0IGluIE5ZQyBpdHNlbGYsIGJ1dCBhcmUgaW4gdGhlIGdyZWF0ZXIgbWV0cm9wb2xpdGFuIGFyZWEuIFRoZSB5ZWxsb3cgYm94IGFib3ZlIHNob3dzIHRoZSB0YXJnZXQgYXJlYS4gTGV0J3Mgb25seSBmaWx0ZXIgb3V0IG9ubHkgdGhvc2Ugd2hpY2ggYXJlIGNsZWFybHkgd3JvbmcuCgoKYGBge3J9CmxvbmdfcmFuZ2UgPC0gYWJzKGFicygtNzQuMjApIC0gYWJzKC03My43MCkpCmxhdF9yYW5nZSA8LSBhYnMoYWJzKDQwLjUwKSAtIGFicyg0MC45NSkpCgp2ZXJ5X3VubGlrZWx5X2dlbyA8LSBsaXN0aW5ncyAlPiUgZmlsdGVyKGxvbmdpdHVkZSA8IC03NC4yMCAtICgxMCAqIGxvbmdfcmFuZ2UpIHwgbG9uZ2l0dWRlID4gLTczLjcwICsgKDEwICogbG9uZ19yYW5nZSkgfCBsYXRpdHVkZSA8IDQwLjUwIC0gKDEwICogbGF0X3JhbmdlKSB8IGxhdGl0dWRlID4gNDAuOTUgKyAoMTAgKiBsYXRfcmFuZ2UpKQoKZ2dwbG90KHZlcnlfdW5saWtlbHlfZ2VvLCBhZXMobG9uZ2l0dWRlLCBsYXRpdHVkZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9yZWN0KHhtaW4gPSAtNzQuMjAgLCB4bWF4ID0gLTczLjcwLCAgIHltaW4gPSA0MC41MCwgeW1heCA9IDQwLjk1LCAgIGZpbGwgPSAicmVkIikKYGBgCgpUaGUgYmxhY2sgcG9pbnRzIGFib3ZlIGFyZSBmb3IgdGhlIDUyIGxpc3RpbmdzIHdpdGggdW5yZWFzb25hYmxlIGNvb3JkaW5hdGVzLiBUaGVpciBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIHZhbHVlcyB3aWxsIGJlIGNoYW5nZWQgdG8gdGhlIG1lZGlhbiByZXNwZWN0aXZlbHkuCgpgYGB7cn0KbGF0X21lZGlhbiA8LSBtZWRpYW4obGlzdGluZ3MkbGF0aXR1ZGUpCmxvbmdfbWVkaWFuIDwtIG1lZGlhbihsaXN0aW5ncyRsb25naXR1ZGUpCgpsaXN0aW5ncyAlPiUgZmlsdGVyKGxpc3RpbmdfaWQgJWluJSB2ZXJ5X3VubGlrZWx5X2dlbyRsaXN0aW5nX2lkKSAlPiUKICBtdXRhdGUobGF0aXR1ZGUgPSBsYXRfbWVkaWFuLAogICAgICAgICBsb25naXR1ZGUgPSBsb25nX21lZGlhbikKYGBgCgpTYXZlIHRoZSAibGlzdGluZ3MiIG9iamVjdCBmb3IgdXNlIGluIHRoZSBuZXh0IHNlY3Rpb24uCmBgYHtyfQpzYXZlUkRTKGxpc3RpbmdzLCBmaWxlID0gIi4uL2RhdGEvb2JqZWN0cy9saXN0aW5ncy5yZHMiKQpzYXZlUkRTKG1hbmFnZXJfaW50ZXJlc3RfcGN0LCBmaWxlID0gIi4uL2RhdGEvb2JqZWN0cy9tYW5hZ2VyX2ludGVyZXN0X3BjdC5yZHMiKQpzYXZlUkRTKGJ1aWxkaW5nX2ludGVyZXN0X3BjdCwgZmlsZSA9ICIuLi9kYXRhL29iamVjdHMvYnVpbGRpbmdfaW50ZXJlc3RfcGN0LnJkcyIpCgpidWlsZGluZ19pbnRlcmVzdF9wY3QKYGBgCgoK